
/*
 * En este archivo implementamos la funcionalidad de los nodos WORKERS. El comportamiento de los mismos es:
 *
 * Los pasos a llevar cabo por los WORKERS son los siguientes.
 *  --> Esperar instrucciones del MASTER.
 *  --> Si la orden del MASTER es ordenar una nueva lista
 *      <> Obtenemos la lista del MASTER
 *      <> Ordenamos la lista mediante MergeSort en forma local.
 *      <> Solicitar nuevas operaciones al MASTER
 *  --> Si la orden del MASTER es enviar la lista a otro nodo.
 *      <> Obtenemos el nodo destino de la lista.
 *      <> Enviamos la lista ordenada previamente al nodo indicado por el MASTER.
 *      <> Finalizar el bucle.
 *  --> Si la orden del MASTER es recibir una lista de otro nodo.
 *      <> Recibimos la lista del nodo indicado por el MASTER.
 *      <> Ordenamos la lista mdiante MergeSort en forma local.
 *      <> Solicitar nuevas operaciones al MASTER.
 *  --> Si el MASTER indica que no debemos hacer mas nada.
 *      <> Finalizar el bucle
 *  --> Fin de programa
 */

#include "worker.h"
#include "mensajes.h"
#include "../common/ordenamiento.h"
#include <mpi.h>
#include <malloc.h>

// Declaración de estructuras locales
struct DATOS_TAREA {
    int idTarea;
    int cantidadDatos;
};

// Declaración de funciones locales
void enviarSolicitudAlMaster();
struct DATOS_TAREA obtenerOrdenesDelMaster();
int *ordenarNuevoVector(int tamanioVector);
void enviarVector(int tamanioVector, int *vector);
int *recibirOrdenarVector(int *vectorActual, int *tamVectorPropio);

/*
 * Implementación de función definida en la cabecera.
 */
void worker() {

    // El worker funciona en un bucle continuo hasta que la orden del master es finalizar

    // En estas variable guardaremos una referencia al ultimo vector que procesó el worker y su tamaño
    int *vectorActual;
    int tamVectorActual;

    int continuarBucle = 1; // Usaremos esta variable para finalizar el bucle
    while (continuarBucle) {

        // Obtenemos la siguiente orden del MASTER
        struct DATOS_TAREA datosTarea = obtenerOrdenesDelMaster();

        // Dependiendo de la tarea asignada, ejecutamos la funcion correspondiente
        switch (datosTarea.idTarea) {

            case ORDENAR_NUEVA_LISTA : {
                // Nos envían un nuevo vector. Lo ordenamos y guardamos, junto con su tamaño
                tamVectorActual = datosTarea.cantidadDatos;
                vectorActual = ordenarNuevoVector(datosTarea.cantidadDatos);
                enviarSolicitudAlMaster();
                break;
            }

            case ENVIAR_LISTA : {
                // Nos piden que enviemos nuestro vector a otro nodo.
                // Lo enviamos y liberamos la memoria que ocupaba, y salimos del bucle
                enviarVector(tamVectorActual, vectorActual);
                free(vectorActual);
                continuarBucle = 0;
                break;
            }

            case RECIBIR_LISTA : {
                // Nos piden que juntemos nuestro vector con otro vector que nos envían.
                int *nuevoVector = recibirOrdenarVector(vectorActual, &tamVectorActual);
                free(vectorActual);
                vectorActual = nuevoVector;
                enviarSolicitudAlMaster();
                break;
            }

            case FINALIZAR : {
                continuarBucle = 0;
                break;
            }

        }
    }
}


/*
 * Envía un mensaje al MASTER solicitándole que se le envíe una nueva tarea
 */
void enviarSolicitudAlMaster() {
    int dummyData = 0;
    MPI_Send(&dummyData, 1, MPI_INT, 0, ESPERANDO_ORDENES, MPI_COMM_WORLD);
}


/*
 * Le informa al MASTER que ya finalizamos nuestro trabajo y que estamos a la espera
 * de una nueva tarea. Se queda esperando la prespuesta del master.
 */
struct DATOS_TAREA obtenerOrdenesDelMaster() {

    // Nos quedamos esperando la respuesta del master.
    MPI_Status mensaje;
    MPI_Probe(0, MPI_ANY_TAG, MPI_COMM_WORLD, &mensaje);

    struct DATOS_TAREA datosTarea;
    MPI_Get_count(&mensaje, MPI_INT, &datosTarea.cantidadDatos);
    datosTarea.idTarea = mensaje.MPI_TAG;

    // Retornamos la tarea asignada por MASTER, (que se envió en el tag del mensaje)
    return datosTarea;

}


/*
 * Obtiene los datos del mensaje que ha enviado el MASTER previamente.
 * Ordena el vector recibido mediante MergeSort.
 */
int *ordenarNuevoVector(int tamanioVector) {

    // Creamos el buffer para recibir el vector enviado por el MASTER
    int *vectorDesord = malloc(sizeof(int) * tamanioVector);

    // Obtenemos el vector a ordenar del mensaje enviado por el MASTER
    MPI_Recv(vectorDesord, tamanioVector, MPI_INT, 0, ORDENAR_NUEVA_LISTA, MPI_COMM_WORLD, MPI_STATUS_IGNORE);

    // Ahora tenemos que ordenar el vector recibido.
    int *vectorOrdenado;
    if (tamanioVector > 1) {
        vectorOrdenado = mergesortRecursivo(vectorDesord, tamanioVector);
        free(vectorDesord);
    } else {
        vectorOrdenado = vectorDesord;
    }
    
    return vectorOrdenado;

}


/*
 * Envía el vector que se ordenó en la tarea anterior, al nodo indicado
 * en el mensaje que envió el MASTER previamente.
 */
void enviarVector(int tamanioVector, int *vector) {

    // Obtenemos el destino del vector, del mensaje enviado por el MASTER
    int nodoDestino;
    MPI_Recv(&nodoDestino, 1, MPI_INT, 0,
            ENVIAR_LISTA, MPI_COMM_WORLD, MPI_STATUS_IGNORE);

    // Enviamos el vector pasado por parametro, al nodo obtenido.
    MPI_Send(vector, tamanioVector, MPI_INT, nodoDestino, TRANSFERIR_LISTA, MPI_COMM_WORLD);

}


/*
 * Obtiene el vector del nodo especificado por el master.
 * Ordena el vetor recibido.
 */
int *recibirOrdenarVector(int *vectorPropio, int *tamVectorPropio) {

    // Obtenemos el nodo del que debemos recibir el vector
    int nodoOrigen;
    MPI_Recv(&nodoOrigen, 1, MPI_INT, 0, RECIBIR_LISTA, MPI_COMM_WORLD, MPI_STATUS_IGNORE);

    // Ahora debemos averiguar la cantidad de datos recibir
    MPI_Status mensaje;
    MPI_Probe(nodoOrigen, TRANSFERIR_LISTA, MPI_COMM_WORLD, &mensaje);
    int tamVectorRemoto;
    MPI_Get_count(&mensaje, MPI_INT, &tamVectorRemoto);

    // Creamos el buffer para recibir el vector enviado por el nodo destino, y lo recibimos
    int *vectorRemoto = malloc(sizeof(int) * tamVectorRemoto);
    MPI_Recv(vectorRemoto, tamVectorRemoto, MPI_INT, nodoOrigen,
            TRANSFERIR_LISTA, MPI_COMM_WORLD, MPI_STATUS_IGNORE);

    // Efectuamos el ordenamiento
    int *vectorOrdenado = juntarVectoresOrdenados(*tamVectorPropio, vectorPropio, tamVectorRemoto, vectorRemoto);
    *tamVectorPropio = *tamVectorPropio + tamVectorRemoto;

    // Liberamos la memoria no requerida y retornamos el vector ordenado
    free(vectorRemoto);
    return vectorOrdenado;
}
